#include "burnpipe.h"

#include <iostream>
#include <opencv2/videoio.hpp>
#include <vector>

#include <opencv2/core/types.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace burn;

struct Box {
  float x1;
  float x2;
  float y1;
  float y2;
};

struct PersonDets {
  Box box;
};

struct HandDets {
  Box box;
};

struct DataSource : public burn::GParam {
  burn::CVoid reset(const CStatus &curStatus) override {}
  cv::Mat src_img;
  int frame_index;
  int timestamp;
};

struct DataOutput : public burn::GParam {
  burn::CVoid reset(const CStatus &curStatus) override {}
  cv::Mat out_img;
  std::vector<float> rotations;
};

struct DataPersonDets : public burn::GParam {
  burn::CVoid reset(const CStatus &curStatus) override {}
  PersonDets person_dets;
};

struct DataHandDets : public burn::GParam {
  burn::CVoid reset(const CStatus &curStatus) override {}
  HandDets hand_dets;
};

class NodeSource : public burn::GNode {
  CStatus init() override {
    CStatus status;
    status = CGRAPH_CREATE_GPARAM(DataSource, "source") return status;
  }

  CStatus run() override {
    CStatus status;
    return status;
  }
};

class NodePersonDet : public burn::GNode {
public:
  CStatus init() override {
    CStatus status;
    status = CGRAPH_CREATE_GPARAM(DataPersonDets, "person_dets") return status;
  }

  CStatus run() override {
    cv::Mat frame;
    auto status = get_source_data(frame);
    BLOG("[%s] read from source node, img: %dx%d", getName().c_str(), frame.rows,
        frame.cols);

    // do detection part
    CGRAPH_SLEEP_SECOND(2);
    auto *myParam =
        CGRAPH_GET_GPARAM_WITH_NO_EMPTY(DataPersonDets, "person_dets");
    {
      CGRAPH_PARAM_WRITE_CODE_BLOCK(myParam)
      myParam->person_dets.box = Box{34, 120, 234, 400};
    }
    return status;
  }

  CStatus get_source_data(cv::Mat &frame) {
    auto myParam = CGRAPH_GET_GPARAM_WITH_NO_EMPTY(DataSource, "source");
    {
      CGRAPH_PARAM_READ_CODE_BLOCK(myParam)
      frame = myParam->src_img;
    }
    CStatus status;
    return status;
  }
};

class NodeHandDet : public burn::GNode {
public:
  CStatus init() override {
    CStatus status;
    status = CGRAPH_CREATE_GPARAM(DataHandDets, "hand_dets") return status;
  }

  CStatus run() override {
    cv::Mat frame;
    auto status = get_source_data(frame);
    BLOG("[%s] read from source node, img: %dx%d", getName().c_str(), frame.rows,
        frame.cols);
    // do detection part
    CGRAPH_SLEEP_SECOND(2);
    auto *myParam = CGRAPH_GET_GPARAM_WITH_NO_EMPTY(DataHandDets, "hand_dets");
    {
      CGRAPH_PARAM_WRITE_CODE_BLOCK(myParam)
      HandDets hand_dets{Box{224., 124., 256., 167.}};
      myParam->hand_dets = hand_dets;
    }
    return status;
  }

  CStatus get_source_data(cv::Mat &frame) {
    auto myParam = CGRAPH_GET_GPARAM_WITH_NO_EMPTY(DataSource, "source");
    {
      CGRAPH_PARAM_READ_CODE_BLOCK(myParam)
      frame = myParam->src_img;
    }
    CStatus status;
    return status;
  }
};

class NodeBody3D : public burn::GNode {
public:
  CStatus run() override {
    CStatus status;
    auto myParam =
        CGRAPH_GET_GPARAM_WITH_NO_EMPTY(DataPersonDets, "person_dets");
    PersonDets person_dets;
    {
      CGRAPH_PARAM_READ_CODE_BLOCK(myParam)
      person_dets = myParam->person_dets;
    }
    BLOG("[%s] read from source node, box: %fx%f", getName().c_str(),
        person_dets.box.x1, person_dets.box.y1);

    // do detection part
    CGRAPH_SLEEP_SECOND(3);
    return status;
  }
};

class NodeHand3D : public burn::GNode {
public:
  CStatus run() override {
    CStatus status;
    auto myParam = CGRAPH_GET_GPARAM_WITH_NO_EMPTY(DataHandDets, "hand_dets");
    HandDets hand_dets;
    {
      CGRAPH_PARAM_READ_CODE_BLOCK(myParam)
      hand_dets = myParam->hand_dets;
    }
    BLOG("[%s] read from source node, box: %fx%f", getName().c_str(),
        hand_dets.box.x1, hand_dets.box.y1);

    // do detection part
    CGRAPH_SLEEP_SECOND(3);
    return status;
  }
};

class NodeFusion : public burn::GNode {
public:
  CStatus init() override {
    CStatus status;
    status = CGRAPH_CREATE_GPARAM(DataOutput, "output") return status;
  }

  CStatus run() override {
    CStatus status;
    // read outputs from body3d and hand3d
    BLOG("[%s] get params from body and hand", getName().c_str());
    // do detection part
    CGRAPH_SLEEP_SECOND(2);

    // get input src image
    cv::Mat frame;
    get_source_data(frame);

    // draw text
    cv::putText(frame, "fusion output", cv::Point(20, 40),
                cv::FONT_HERSHEY_COMPLEX, 3.5, cv::Scalar(0, 255, 0));

    // write data
    auto *myParam = CGRAPH_GET_GPARAM_WITH_NO_EMPTY(DataOutput, "output");
    {
      CGRAPH_PARAM_READ_CODE_BLOCK(myParam)
      myParam->out_img = frame;
    }
    return status;
  }

  CStatus get_source_data(cv::Mat &frame) {
    auto myParam = CGRAPH_GET_GPARAM_WITH_NO_EMPTY(DataSource, "source");
    {
      CGRAPH_PARAM_READ_CODE_BLOCK(myParam)
      frame = myParam->src_img;
    }
    CStatus status;
    return status;
  }
};

void tutorial_param(std::string data_f) {
  GPipelinePtr pipeline = GPipelineFactory::create();
  CStatus status;
  GElementPtr a, b, c, d, e, f = nullptr;

  // status += pipeline->registerGElement<NodeSource>(&a, {}, "personNode");
  // status += pipeline->registerGElement<NodePersonDet>(&a, {}, "personNode");
  // status += pipeline->registerGElement<NodeHandDet>(&b, {}, "handNode");
  // status += pipeline->registerGElement<NodeBody3D>(&c, {a}, "body3dNode");
  // status += pipeline->registerGElement<NodeHand3D>(&d, {b}, "hand3dNode");
  // status += pipeline->registerGElement<NodeFusion>(&e, {c, d}, "fusionNode");

  status += pipeline->registerGElement<NodeSource>(&a, {}, "soureceNode");
  status += pipeline->registerGElement<NodePersonDet>(&b, {a}, "personNode");
  status += pipeline->registerGElement<NodeHandDet>(&c, {a}, "handNode");
  status += pipeline->registerGElement<NodeBody3D>(&d, {b}, "body3dNode");
  status += pipeline->registerGElement<NodeHand3D>(&e, {c}, "hand3dNode");
  status += pipeline->registerGElement<NodeFusion>(&f, {d, e}, "fusionNode");
  if (!status.isOK()) {
    return;
  }

  status += pipeline->init();

  BLOG("runing on file: %s", data_f.c_str());

  cv::VideoCapture cap;
  cap.open(data_f);
  if (!cap.isOpened()) {
    std::cerr << "Could not open device or video file!" << std::endl;
  }

  cv::Mat frame;
  int i = 0;
  BLOG("start runing pipe...");

  while (cap.read(frame)) {
    auto data = pipeline->getGParam<DataSource>("source");
    data->src_img = frame;
    data->frame_index = i;

    BPTIMER_START(all);
    status += pipeline->run();
    BPTIMER_STOP(all);
    std::cout << "========> [burn] loop : " << i + 1
              << ", and run status = " << status.getCode()
              << ", all time: " << BPTIMER_MSEC(all) / 1000. << std::endl;
    i += 1;

    auto out = pipeline->getGParam<DataOutput>("output");
    cv::imshow("Burnpipe", out->out_img);
    if (cv::waitKey(1) > 0) {
      break;
    }
  }

  status += pipeline->destroy();
  GPipelineFactory::remove(pipeline);
}

int main(int argc, char *argv[]) {
  std::string data_f = argv[1];
  tutorial_param(data_f);
  return 0;
}
